# 一、原子性操作方法

# 1.1 同步锁

 synchronized (this){
   ++ count;
}
1
2
3

# 1.2 Atomic类

  • Atomic底层利用CAS实现,而CAS则是利用UnSafe类(native) + 反射技术实现,通过do...while(自旋)的模式,每次都会检查变量是否被修改过,修改过则while成立并重做。
private AtomicLong acount = new AtomicLong(0L);
acount.incrementAndGet();
1
2

# 1.3 LongAdder类

  • LongAdder为JDK8提供的多线程技术类,底层是利用分而治之的思想,即为每个线程分配一个计数副本,线程之间的副本相互隔离,最终将所有副本相加,多线程下计数速度最快。
private LongAdder lacount = new LongAdder();
// 计数
lacount.increment();
// 获取结果
lacount.sum()
1
2
3
4
5

# 1.4 Atomic类实现原理-CAS

    1. 通过Unsafe获取对象属性偏移量
    1. 利用Unsafe的CMS方法及获取到的偏移量实现自增(操作内存)
public class LockDemo1 {
    private volatile int value = 0;
    // 直接操作内存,修改对象、数组内存等
    private static Unsafe unsafe;
    // 获取属性偏移量
    private static long valueOfferset;

    static {
        try {
            //反射技术获取unsafe值
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get(null);

            // 获取到value属性偏移量(用于定位value属性在内存中的具体地址)
            valueOfferset = unsafe.objectFieldOffset(LockDemo1.class.getDeclaredField("value"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    public void add() {
        // i++; 在Java层面分为三个步骤
        // CAS + 循环重试(自旋)
        int current;
        do {
            // 操作耗时的话线程会占用大量的CPU执行时间
            current = unsafe.getIntVolatile(this, valueOfferset);
        } while (!unsafe.compareAndSwapInt(this, valueOfferset, current, current + 1));
        // 执行失败则重试
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo1 lockDemo1 = new LockDemo1();

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    lockDemo1.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(lockDemo1.value);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 二、死锁

# 2.1 死锁的概念

在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。

那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。

# 2.2 死锁的条件

# 互斥条件

同一个资源在任何时刻最多只能被一个线程持有

image-20221121000001548

# 持有并等待条件

持有锁1的线程会等待锁2,等待过程不会释放自己持有的锁

image-20221121000104137

# 不可剥夺条件

线程A持有的锁只能由自己释放,无法被其他线程释放。

image-20221121000403080

# 环路等待条件

即至少需要两个锁资源,并且存在多线程抢占,最终形成了环形链。

![image-20221121000611876](/Users/zengbojia/Library/Application Support/typora-user-images/image-20221121000611876.png)

# 2.3 死锁的排查

# jps + jstack排查死锁问题

  1. jps查看当前正运行的java进程

img

  1. jstack + 进程号,可以查看当前的线程栈信息,定位到死锁的位置并进行优化

img

# jconsole排查死锁问题

image-20221121003502330

image-20221121003520877

# VisualVM排查死锁问题

image-20221121003610026

image-20221121003632598

点击“线程Dump”按钮,dump下堆栈信息,定位产生死锁的代码位置

image-20221121003732116

# 2.4 如何避免死锁

避免死锁的方式就是打破上面产生死锁的其中一个条件

  • 打破持有并等待条件:超时释放锁
  • 打破不可剥夺条件:可强制释放锁
  • 打破环路等待条件:资源有序分配法

资源有序分配是最常用的方法,即对于需要获取A,B两个锁的所有线程,在代码层面上都通过先获取A锁,再获取B锁,保证不会出现环路。

image-20221121001144961

# 参考

Last Updated: 2022-11-21 12:53:50 ├F10: AM┤